package com.gframework.mybatis.dao.mybatis.plugins;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.builder.StaticSqlSource;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMapping;
import org.apache.ibatis.mapping.ResultSetType;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectionException;
import org.apache.ibatis.reflection.wrapper.ObjectWrapper;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;

/**
 * 删除信息处理抽象类.<br>
 * 本类提供了在做select，update等操作过程中的一些参数转换，取得新MappedStatement以及ParameterMapping的方法
 * 
 * @since 1.0.0
 * @author Ghwolf
 */
public abstract class AbstractDeleteInfo implements DeleteInfo {

	/**
	 * BoundSql类的additionalParameters属性的field对象
	 */
	protected static final Field BOUND_SQL_ADDITIONAL_PARAMETERS_FIELD;
	/**
	 * MappedStatement类的sqlCommandType属性的Field镀锡
	 */
	protected static final Field SQL_COMMAND_TYPE_FIELD;
	
    private static final List<ResultMapping> EMPTY_RESULTMAPPING = new ArrayList<>(0);

	static {
		try {
			BOUND_SQL_ADDITIONAL_PARAMETERS_FIELD = BoundSql.class.getDeclaredField("additionalParameters");
			BOUND_SQL_ADDITIONAL_PARAMETERS_FIELD.setAccessible(true);
			SQL_COMMAND_TYPE_FIELD = MappedStatement.class.getDeclaredField("sqlCommandType");
			SQL_COMMAND_TYPE_FIELD.setAccessible(true);
		} catch (Exception e) {
			throw new UnsupportedOperationException(
					"当前mybatis版本无法支持DeleteHelper操作，因为BoundSql类没有additionalParameters成员属性！或MappedStatement类没有sqlCommandType成员属性。", e);
		}
	}

	/**
	 * 取得一个查询操作的MappedStatement对象.<br>
	 * 
	 * @param ms 原MappedStatement对象
	 * @param sqlSource SqlSource类对象
	 * @return 返回一个新的可以执行查询操作的MappedStatement类对象
	 */
	protected MappedStatement getSelectMappedStatement(MappedStatement ms,SqlSource sqlSource) {
		String id = ms.getId() + "__FIND";
		MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), id,
				sqlSource, SqlCommandType.SELECT);
		builder.resource(ms.getResource());
		builder.fetchSize(ms.getFetchSize());
		builder.statementType(ms.getStatementType());
		builder.keyGenerator(ms.getKeyGenerator());
		if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
			StringBuilder keyProperties = new StringBuilder();
			for (String keyProperty : ms.getKeyProperties()) {
				keyProperties.append(keyProperty).append(",");
			}
			keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
			builder.keyProperty(keyProperties.toString());
		}
		builder.timeout(ms.getTimeout());
		builder.parameterMap(ms.getParameterMap());
		// count查询返回值int
		List<ResultMap> resultMaps = new ArrayList<>();

		ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), id, Map.class, EMPTY_RESULTMAPPING)
				.build();
		resultMaps.add(resultMap);
		builder.resultMaps(resultMaps);
		builder.resultSetType(ResultSetType.SCROLL_INSENSITIVE);
		builder.useCache(false);

		return builder.build();
	}

	/**
	 * 取得一个新的update操作的SqlSource类对象及参数对象.<br>
	 * 
	 * @param editParam 要进行修改的字段信息，key=列名称，value=修改后的值
	 * @return 返回一个SqlSourceParamVo类对象，其中包含了SqlSource和请求参数对象
	 * @see MetaObject
	 * @see MultipartGetterObjectWrapper
	 */
	protected SqlSourceParamVo createUpdateSqlSource(Map<String, Object> editParam) {
		MappedStatement ms = this.getMappedStatement();
		StringBuilder updateSql = new StringBuilder("UPDATE " + this.getTableName() + " SET ");

		List<ParameterMapping> sourceParamMappingList = this.getBoundSql().getParameterMappings();
		// 是否包含原始参数映射信息
		boolean hasSourceParamMapping = sourceParamMappingList != null && !sourceParamMappingList.isEmpty();
		// 新参数映射信息集合长度
		int paramMappingSize = hasSourceParamMapping ? sourceParamMappingList.size() + editParam.size()
				: editParam.size();
		List<ParameterMapping> newParamMappingList = new ArrayList<>(paramMappingSize);

		// 后面的参数会设置到这里
		Map<String, Object> otherParam = new HashMap<>();

		TypeHandlerRegistry typeHandlerReg = ms.getConfiguration().getTypeHandlerRegistry();
		// 将update参数设置到新的paramMapping中和参数Map中。
		for (Map.Entry<String, Object> entry : editParam.entrySet()) {
			updateSql.append(entry.getKey()).append("=?,");
			String key = "!u_" + entry.getKey();
			Object value = entry.getValue();
			Class<?> type = value == null ? String.class : value.getClass();

			otherParam.put(key, value);
			TypeHandler<?> th = typeHandlerReg.getTypeHandler(type);
			if (th == null) {
				newParamMappingList.add(
						new ParameterMapping.Builder(ms.getConfiguration(), key, type).build());
			} else {
				newParamMappingList.add(
						new ParameterMapping.Builder(ms.getConfiguration(), key, th).build());
			}
		}
		updateSql.delete(updateSql.length() - 1, updateSql.length()).append(' ');
		Map<String, Object> additionParameter = this.getAdditionParameter();
		if (!additionParameter.isEmpty()) {
			otherParam.putAll(additionParameter);
		}

		// 将原始参数添加进去
		if (hasSourceParamMapping) {
			newParamMappingList.addAll(sourceParamMappingList);
		}

		updateSql.append(this.getWhere());
		SqlSource sqlSource = new StaticSqlSource(ms.getConfiguration(), updateSql.toString(),
				newParamMappingList);
		// 由于这类修改了原始参数类型为ObjectWrapper，所以mybatis不会为其生成_parameter参数
		// 由于MetaObject如果获取到了一个不存在的key，会出现异常，但是这里的顺序时第一个时Map的MetaObject，他不会出现异常，第二个则是原始对象的MetaObject，因此不会出现问题。
		otherParam.putIfAbsent("_parameter", this.getOriginalParam());
		return new SqlSourceParamVo(sqlSource, getMultipartObjectWrapper(otherParam));
	}

	/**
	 * 取得一个封装了原始参数和指定其他参数的 MultipartGetterObjectWrapper 类对象
	 * 
	 * @param otherParam 其他类型的参参数对象
	 * @return 返回 MultipartGetterObjectWrapper 类对象
	 */
	protected MultipartGetterObjectWrapper getMultipartObjectWrapper(Object otherParam) {
		ObjectWrapper newWrapper = this.getMappedStatement().getConfiguration().newMetaObject(otherParam).getObjectWrapper();
		ObjectWrapper originalWrapper = this.toObjectWrapper(this.getMappedStatement().getConfiguration(), this.getOriginalParam());
		return new MultipartGetterObjectWrapper(newWrapper,originalWrapper);
	}
	
	/**
	 * 修改MappedStatement的SqlCommandType类型
	 */
	protected void changeCommentType(MappedStatement ms,SqlCommandType type) {
		try {
			SQL_COMMAND_TYPE_FIELD.set(ms, type);
		} catch (Exception e) {
			throw new ReflectionException("设置MappedStatement类的SqlCommandType属性失败！",e);
		}
	}

	private ObjectWrapper toObjectWrapper(Configuration configuration, Object obj) {
		if (obj instanceof MetaObject) {
			return ((MetaObject) obj).getObjectWrapper();
		} else if (obj instanceof ObjectWrapper) {
			return (ObjectWrapper) obj;
		} else {
			return configuration.newMetaObject(obj).getObjectWrapper();
		}
	}
	
	/**
	 * 取得原始的boundSql类对象
	 * 
	 * @return 返回BoundSql类对象
	 */
	protected abstract BoundSql getBoundSql();

	/**
	 * 取得原始的MappedStatement类对象
	 * 
	 * @return 返回MappedStatement类对象
	 */
	protected abstract MappedStatement getMappedStatement();

	/**
	 * 取得原始参数对象
	 * 
	 * @return 返回原始传入的参数对象
	 */
	protected abstract Object getOriginalParam();
	/**
	 * 取得mybatis的执行器，可以执行其他类型的sql.
	 * 前提是拦截器必须拦截的是Executor接口方法。
	 * @return Executor类对象 
	 */
	protected abstract Executor getExecutor() ;

	/**
	 * 取得BoundSql中的附加参数map集合（不会出现异常也不会返回null），如果没有则集合长度为0
	 */
	@SuppressWarnings("unchecked")
	protected Map<String, Object> getAdditionParameter() {
		try {
			Map<String, Object> m = (Map<String, Object>) BOUND_SQL_ADDITIONAL_PARAMETERS_FIELD.get(this.getBoundSql());
			return m == null ? Collections.emptyMap() : m;
		} catch (Exception e) {
			this.getMappedStatement().getStatementLog().error("DeleteInfo操作时，获取附加参数出错！", e);
			return Collections.emptyMap();
		}
	}
}
